package login;

import java.io.InputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import game.common.StringConstants;
import game.db.DataSourceManager;
import game.session.GlobalTimer;
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;
import io.netty.util.AttributeKey;
import lion.common.IdWorker;
import lion.common.MsgCodeAnn;
import lion.common.NamedThreadFactory;
import lion.common.ScheduledThreadPoolExecutorLog;
import lion.http.HttpMapUri;
import lion.http.Netty4HttpServer;
import lion.lua.LuaServer;
import lion.netty4.core.BaseProtoIoExecutor;
import lion.netty4.core.MyIoExecutor;
import lion.netty4.core.ServerStat;
import lion.netty4.filter.MsgAccessLimitCache;
import lion.netty4.message.GamePlayer;
import lion.netty4.message.IGameServer;
import lion.netty4.message.MsgDispatcher;
import lion.netty4.message.MyRequestMessage;
import lion.netty4.message.RequestMessageRaw;
import lion.netty4.message.RequestProtoMessage;
import lion.netty4.processor.MsgProcessor;
import login.bean.LoginClientSessionBean;
import login.http.ScriptRunNettyHandler;
import login.http.StatHandler;
import login.logic.LoginLanClientManager;
import login.logic.LoginLanServerManager;

public class LoginServer implements IGameServer {

	public static final AttributeKey<LoginClientSessionBean> KEY_LOGIN_SESSION_INFO = AttributeKey
			.valueOf(".LOGIN_SESSION_INFO");

	private static Logger logger = LoggerFactory.getLogger(LoginServer.class);

	private LuaServer netty4Server;

	private static LoginServer instance;

	private volatile LoginServerConfig serverConfig;

	private Thread shutdownHook;

//	private Server jettyServer;
	
	private Netty4HttpServer netty4HttpServer;

	public static MsgDispatcher<MsgProcessor> msgDispatcher = new MsgDispatcher<MsgProcessor>();

	private LoginLanClientManager lanServerManager;

	/** 数据库操作使用的线程池 */
	public static ScheduledExecutorService executorService;

	private IdWorker uuIdGenerator;

	public static LoginServer getInstance() {
		return instance;
	}

	public LoginLanClientManager getLanClientManager() {
		return lanServerManager;
	}

	public LoginServerConfig getLoginServerConfig() {
		return serverConfig;
	}

	public static void main(String[] args) {
		try {
			new LoginServer().start();
		} catch (Exception e) {
			logger.error("", e);
		}
	}

	public long generateRandomId() {
		return uuIdGenerator.nextId();
	}

	private void start() throws Exception {
		instance = this;
		long startTime = System.currentTimeMillis();
		uuIdGenerator = new IdWorker(1);
		serverConfig = loadConfig();
		DataSourceManager.init(serverConfig.getMysqlUrl(), serverConfig.getMysqlClassname(),
				serverConfig.getMysqlUsername(), serverConfig.getMysqlPassword());
		registMessageProcessor();
		BaseProtoIoExecutor.setGameServer(this);
		MyIoExecutor.setGameServer(this);
		logger.warn("start netty server");
		netty4Server = new LuaServer(serverConfig.getSocketPort());
		netty4Server.run();
		// init login session manager
		LoginSessionManager.getInstance();
		// load template
//		loadTemplates();
		logger.warn("init lan connection");
		startLanServer();
		initHttpServer();
		// init lan server
		LoginLanServerManager.getInstance().startServer(serverConfig.getLanPort());
		// 初始化线程池
		executorService = new ScheduledThreadPoolExecutorLog(Runtime.getRuntime().availableProcessors() * 6,
				new NamedThreadFactory("executorPool"));
		addShutDownHook();
		long endTime = System.currentTimeMillis();
		logger.warn("login server startup success! in {} s", (endTime - startTime) / 1000);
		ServerStat.startUpTime = endTime;
	}

	private void startLanServer() {
		logger.info("start lan server!");
		lanServerManager = new LoginLanClientManager();
//		List<ServerList4Db> list4Dbs = ServerListManager.getInstance().getServerList();
//		for (ServerList4Db serverList4Db : list4Dbs) {
//			lanServerManager.connect(serverList4Db.getIp(), serverList4Db.getLanPort());
//		}
		lanServerManager.connect(serverConfig.getHallServerLanHost(),serverConfig.getHallServerLanPort());
	}

	private LoginServerConfig loadConfig() {
		logger.warn("load executer");
		InputStream propStream = null;
		Properties properties = new Properties();
		String serverConfStr = LoginServerConfig.CONFIG_FILE_NAME;
		try {
			propStream = LoginServer.class.getClassLoader().getResourceAsStream(serverConfStr);
			if (propStream == null) {
				propStream = LoginServer.class.getClassLoader().getResourceAsStream("/" + serverConfStr);
				properties.load(propStream);
			} else {
				properties.load(propStream);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		LoginServerConfig serverConfig = new LoginServerConfig();
		serverConfig.setSocketPort(Integer.valueOf(properties.getProperty(LoginServerConfig.KEY_SOCKET_PORT)));
		serverConfig.setLanPort(Integer.valueOf(properties.getProperty(LoginServerConfig.KEY_LAN_PORT)));
		serverConfig.setHttpPort(Integer.valueOf(properties.getProperty(LoginServerConfig.KEY_HTTP_PORT)));
		serverConfig.setMysqlUrl(properties.getProperty(ServerConfig.KEY_MYSQL_URL));
		serverConfig.setMysqlClassname(properties.getProperty(ServerConfig.KEY_MYSQL_CLASSNAME));
		serverConfig.setMysqlUsername(properties.getProperty(ServerConfig.KEY_MYSQL_USERNAME));
		serverConfig.setMysqlPassword(properties.getProperty(ServerConfig.KEY_MYSQL_PASSWORD));
		serverConfig.setAnnouncement(properties.getProperty(LoginServerConfig.KEY_announcement));
		serverConfig.setCodeVersion(Integer.valueOf(properties.getProperty(LoginServerConfig.KEY_CODE_VERSION)));
		serverConfig.setCodeReview(Integer.valueOf(properties.getProperty(LoginServerConfig.KEY_CODE_REVIEW)));
		String lanAllowIps = properties.getProperty(LoginServerConfig.KEY_LAN_ALLOW_IP);
		String[] allowIps = StringUtils.split(lanAllowIps, StringConstants.SEPARATOR_DI);
		Set<String> allowIpSet = new HashSet<>();
		for (String aIp : allowIps) {
			allowIpSet.add(aIp);
		}
		serverConfig.setLanAllowIps(allowIpSet);
		// 设置白名单
		String whiteListStr = properties.getProperty(LoginServerConfig.KEY_LOGIN_WHITE_LIST);
		String[] whiteListStrs = StringUtils.split(whiteListStr, StringConstants.SEPARATOR_DI);
		Set<String> whiteListSet = new HashSet<>();
		for (String aIp : whiteListStrs) {
			whiteListSet.add(aIp);
		}
		serverConfig.setLoginWhiteList(whiteListSet);
		serverConfig.setAssetBundleUrl(properties.getProperty(LoginServerConfig.ASSET_BUNDLE_URL_KEY));
		serverConfig.setHallServerSocketHost(properties.getProperty("hall_server.socket.host"));
		serverConfig.setHallServerSocketPort(Integer.valueOf(properties.getProperty("hall_server.socket.port")));
		serverConfig.setHallServerLanHost(properties.getProperty(LoginServerConfig.KEY_hall_server_lan_host));
		serverConfig.setHallServerLanPort(Integer.valueOf(properties.getProperty(LoginServerConfig.KEY_hall_server_lan_port)));
		
		serverConfig.setWeiXinAppId(properties.getProperty("weixin.AppID"));
		serverConfig.setWeiXinAppSecret(properties.getProperty("weixin.AppSecret"));
		return serverConfig;
	}

	public void reloadServerConfig() {
		serverConfig = loadConfig();
	}

	private void registMessageProcessor() {
		// 注册消息
		logger.warn("regist message processors!");
		Map<Integer, Integer> accessIntervalLimitMap = new HashMap<Integer, Integer>();
		FastClasspathScanner fastClasspathScanner = new FastClasspathScanner("login");
		List<String> list1 = fastClasspathScanner.scan().getNamesOfClassesWithAnnotation(MsgCodeAnn.class);
		try {
			for (String processorClassName : list1) {
				Class<?> processorClass = Class.forName(processorClassName);
				MsgCodeAnn ann = processorClass.getAnnotation(MsgCodeAnn.class);
				Object msgProcessorObj = processorClass.newInstance();
				if (msgDispatcher.containsMsgcode(ann.msgcode())) {
					logger.error("duplicate msgcode={}", ann.msgcode());
				}
				msgDispatcher.addMsgProcessor(ann.msgcode(), (MsgProcessor) msgProcessorObj);
				accessIntervalLimitMap.put(ann.msgcode(), ann.accessLimit());
			}
		} catch (Exception e) {
			logger.error("", e);
		}
//		Reflections reflections = new Reflections("login");
//		Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(MsgCodeAnn.class);
//		try {
//			for (Class<?> processorClass : annotated) {
//				MsgCodeAnn ann = processorClass.getAnnotation(MsgCodeAnn.class);
//				Object msgProcessorObj = processorClass.newInstance();
//				if (msgDispatcher.containsMsgcode(ann.msgcode())) {
//					logger.error("duplicate msgcode={}", ann.msgcode());
//				}
//				msgDispatcher.addMsgProcessor(ann.msgcode(), (MsgProcessor) msgProcessorObj);
//				accessIntervalLimitMap.put(ann.msgcode(), ann.accessLimit());
//			}
//		} catch (Exception e) {
//			logger.error("", e);
//		}
		// 注册IO消息访问频率控制
		MsgAccessLimitCache.getInstance().initMsgTimeTemplate(accessIntervalLimitMap);
		// 打印processor信息
		msgDispatcher.dumpProcessors();
	}

	private void addShutDownHook() {
		shutdownHook = new Thread("shutdown-hook") {
			public void run() {
				shutdown();
			}
		};
		Runtime.getRuntime().addShutdownHook(shutdownHook);
	}

	private void initHttpServer() {
		try {
			logger.warn("init http server,port={}!", serverConfig.getHttpPort());
			// use default backlog value
			// httpServer = HttpServer.create(new
			// InetSocketAddress(serverConfig.getHttpPort()), 0);
			// //httpServer.createContext("/console", new
			// LoginConsoleHandler());
			// //httpServer.createContext("/admin", new LoginControlHandler());
			// httpServer.createContext("/script", new ScriptRunHandler());
			// httpServer.createContext("/pay_notify", new
			// PaymentCallbackHanlder());
			// httpServer.createContext("/role_query", new
			// SifubaRoleQueryHanlder());
			// httpServer.setExecutor(null); // creates a default executor
			// httpServer.start();
			
//			// The Server
//			jettyServer = new Server();
//
//			// HTTP connector
//			ServerConnector http = new ServerConnector(jettyServer);
//			// http.setHost("localhost");
//			http.setPort(serverConfig.getHttpPort());
//			http.setIdleTimeout(5000);
//			// Set the connector
//			jettyServer.addConnector(http);
//			// Set handler
//			ContextHandler context = new ContextHandler();
//			context.setContextPath("/");
//			context.setHandler(new AbstractHandler() {
//
//				@Override
//				public void handle(String target, Request baseRequest, HttpServletRequest request,
//						HttpServletResponse response) throws IOException, ServletException {
//					target = target.substring(1);
//					if (target.equals("script")) {
//						ScriptRunJettyHandler.getInstance().handle(target, baseRequest, request, response);
//					} else if (target.equals("pay_notify")) {
//						PaymentCallbackJettyHanlder.getInstance().handle(target, baseRequest, request, response);
//					} else if (target.equals("zl_callback")) {
//						YijiePayCallbackHanlder.getInstance().handle(target, baseRequest, request, response);
//					}
//				}
//			});
//			jettyServer.setHandler(context);
//
//			// ContextHandler contextScript = new ContextHandler("/script");
//			// contextScript.setHandler(new ScriptRunJettyHandler());
//			//
//			// ContextHandler contextPayNotify = new
//			// ContextHandler("/pay_notify");
//			// contextPayNotify.setHandler(new PaymentCallbackJettyHanlder());
//			// //
//			// ContextHandler contextRoleQuery = new
//			// ContextHandler("/role_query");
//			// contextRoleQuery.setHandler(new SifubaRoleQueryJettyHanlder());
//			//
//			// ContextHandlerCollection contexts = new
//			// ContextHandlerCollection();
//			// contexts.setHandlers(new Handler[] { contextScript,
//			// contextPayNotify, contextRoleQuery });
//
//			// jettyServer.setHandler(contexts);
//
//			// Start the server
//			jettyServer.start();
//			// jettyServer.join();
			
			
			// netty4server
			netty4HttpServer = new Netty4HttpServer(serverConfig.getHttpPort());
//			HttpMapUri mapHanlder = new HttpMapUri(new ScriptRunNettyHandler("/script"),
//					new PaymentCallbackJettyHanlder("/pay_notify"), new YijiePayCallbackHanlder("/zl_callback"));
			HttpMapUri mapHanlder = new HttpMapUri(new ScriptRunNettyHandler("/script"),new StatHandler("/stat"));
			netty4HttpServer.start(executorService, mapHanlder);
		} catch (Exception e) {
			logger.error("", e);
		}
	}

	public void removeShutDownHook() {
		if (shutdownHook != null) {
			Runtime.getRuntime().removeShutdownHook(shutdownHook);
		}
	}

	public void shutdown() {
		logger.warn("shutting down login server!");
		netty4Server.shutdown();
		logger.warn("shot down lan server!");
		lanServerManager.shutdown();
		LoginLanServerManager.getInstance().shutdown();
		logger.warn("close http server!");
		netty4HttpServer.shutdown();
		// close timer
		logger.warn("global timmer shutdown!");
		GlobalTimer.getInstance().shutdown();
		// logger.warn("begin threadpool shutdown!");
		// try {
		// executorService.shutdown();
		// while (!executorService.isTerminated()) {
		// executorService.awaitTermination(5, TimeUnit.SECONDS);
		// }
		// logger.warn("executorServiceForDB terminate successfully!");
		// } catch (Exception e) {
		// logger.error("", e);
		// }
//		try {
//			jettyServer.stop();
//		} catch (Exception e) {
//			logger.error("stop jetty server error!", e);
//		}
		// close db
		DataSourceManager.getInstance().shutdown();
		logger.warn("successfully close login server!");
	}

	@Override
	public boolean checkIP(String address) {
		return true;
	}

	@Override
	public void syncExecuteIoRequest(GamePlayer player, RequestMessageRaw request) {
		// NULL
	}

	@Override
	public void syncExecuteIoRequest(GamePlayer player, RequestProtoMessage request) {
		// NULL
	}

	@Override
	public MsgProcessor getMsgProcessor(int msgCode) {
		return msgDispatcher.getMsgProcessor(msgCode);
	}

	@Override
	public void syncExecuteIoRequest(GamePlayer player, MyRequestMessage requestMsg) {
		// NULL
	}

}
